package com.vteba.security.meta;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.vteba.common.constant.CommonConst;
import com.vteba.security.SecurityContextUtils;
import com.vteba.security.spi.AuthoritiesService;
import com.vteba.security.user.Authority;
import com.vteba.security.user.IUserDetails;
import com.vteba.security.user.Resource;

/**
 * 实现FilterInvocationSecurityMetadataSource接口，进行url级别的拦截，使用servlet filter<br>
 * 实现MethodSecurityMetadataSource接口，进行method级别的拦截，使用aop <br>
 * 初始化加载系统所有的权限以及权限和资源的对应关系。
 * @author yinlei
 */
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {
//	private static final Pattern STATIC_FILE = Pattern.compile("^(.*)+.(css|js|jpg|jpeg|gif|png|bmp)$");
	
	private AuthoritiesService authoritiesServiceImpl;
	// 是否验证资源
	private boolean verifyResource;
	// 是否将资源作为权限
	private boolean resourceAsAuth;
	// 是否是多租户模式，默认否
	private boolean tenantSchema;
	
	// 白名单租户id，不验证证
	private List<String> whiteListTenantIds = new ArrayList<>();
	
	private Map<String, Collection<ConfigAttribute>> resourceMap = new HashMap<String, Collection<ConfigAttribute>>();

	public FilterInvocationSecurityMetadataSourceImpl(AuthoritiesService authoritiesServiceImpl) {
		super();
		this.authoritiesServiceImpl = authoritiesServiceImpl;
		//加载资源和权限
		this.loadResourceAuthConfig();
	}
	
	/**
	 * 从数据库加载资源和权限的对应关系
	 */
	private void loadResourceAuthConfig() {
		List<? extends Authority> authList = authoritiesServiceImpl.getAuthorities();
		for (Authority auth : authList) {
			ConfigAttribute ca = new ConfigAttributeImpl(auth); // 构造一个权限属性
			List<? extends Resource> resourceList = authoritiesServiceImpl.getUrlResource(auth);
			for (Resource url : resourceList) {
				// 该资源和权限是否有对应关系，如果已经存在，则将新权限添加到对应的资源上
				if (resourceMap.containsKey(url.getKey())) {
					Collection<ConfigAttribute> attributes = resourceMap.get(url.getKey());
					attributes.add(ca);
					resourceMap.put(url.getKey(), attributes);
				} else {// 如果是新资源，则将权限添加到对应的资源上
					Collection<ConfigAttribute> atts = new HashSet<ConfigAttribute>();
					if (isResourceAsAuth()) { // 如果将资源也作为权限，将资源作为权限
						ConfigAttribute rescource = new ConfigAttributeImpl(url.getResource(), url.getTenantId());
						atts.add(rescource);
					}
					atts.add(ca);
					resourceMap.put(url.getKey(), atts);
				}
			}
		}
	}

	/**
	 * 获得该url需要的权限
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		FilterInvocation filter = (FilterInvocation) object;

		IUserDetails userDetails = SecurityContextUtils.getCurrentUserInfo();
		if (userDetails == null) {
			return null;
		}
		String tenantId = userDetails.getTenantId();
		if (isTenantSchema()) {
			if (tenantId == null) {
				return null;
			}
		}
		
		for (String resUrl : resourceMap.keySet()) {
			String[] resource = StringUtils.split(resUrl, CommonConst.SHARP);
			if (ArrayUtils.isEmpty(resource)) {
				continue;
			}
			if (isTenantSchema()) {
				String tenant = resource[1]; // 这个是tenantId
				if (tenant != null && !whiteListTenantIds.contains(tenant)) { //如果非空，且不是默认的，验证她
					if (!tenantId.equals(resource[1])) {
						continue;
					}
				}
			}
			AntPathRequestMatcher matcher = new AntPathRequestMatcher(resource[0]);
			if (matcher.matches(filter.getRequest())) {
				return resourceMap.get(resUrl);
			}
		}
		return null;
	}

	/**
	 * 获得所有的权限
	 */
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
		for (Map.Entry<String, Collection<ConfigAttribute>> entry : resourceMap.entrySet()) {
			for (ConfigAttribute attrs : entry.getValue()) {
				allAttributes.add(attrs);
			}
		}
		return allAttributes;
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}

	public boolean isVerifyResource() {
		return verifyResource;
	}

	public void setVerifyResource(boolean verifyResource) {
		this.verifyResource = verifyResource;
	}

	public boolean isResourceAsAuth() {
		return resourceAsAuth;
	}

	public void setResourceAsAuth(boolean resourceAsAuth) {
		this.resourceAsAuth = resourceAsAuth;
	}
	
	/**
	 * use {@link #setWhiteListTenantIds(List)}
	 * @param filterTenantIds
	 */
	@Deprecated
	public void setFilterTenantIds(List<String> filterTenantIds) {
		this.whiteListTenantIds = filterTenantIds;
	}

	public boolean isTenantSchema() {
		return tenantSchema;
	}

	public void setTenantSchema(boolean tenantSchema) {
		this.tenantSchema = tenantSchema;
	}

	public List<String> getWhiteListTenantIds() {
		return whiteListTenantIds;
	}

	public void setWhiteListTenantIds(List<String> whiteListTenantIds) {
		this.whiteListTenantIds = whiteListTenantIds;
	}
}
